using System;

namespace PropertyExpression.Common
{
    public static class OptionalResult
    {
        public static IOptionalResult<TResult> AsOptionalResult<TResult>()
        {
            return new NotExistOptionalResult<TResult>();
        }

        public static IOptionalResult<TResult> AsOptionalResult<TResult>(
            this TResult result)
        {
            return new ExistOptionalResult<TResult>(result);
        }

        public static void Execute<TResult>(
            this IOptionalResult<TResult> optionalResult, 
            Action<TResult> existAction, 
            Action notExistAction)
        {
            optionalResult.Execute(existAction.ToFunc(), notExistAction.ToFunc());
        }

        public static void ExecuteIfExist<TResult>(
            this IOptionalResult<TResult> optionalResult, 
            Action<TResult> existAction)
        {
            Execute(optionalResult, existAction, () => { });
        }

        public static TResult GetValueOrDefault<TResult>(
            this IOptionalResult<TResult> optionalResult)
        {
            return optionalResult.Execute(result => result, () => default(TResult));
        }

        public static bool HasValue<TResult>(
            this IOptionalResult<TResult> optionalResult)
        {
            return optionalResult.Execute(delegate { return true; }, () => false);
        }

        public static TResult GetValue<TResult>(
            this IOptionalResult<TResult> optionalResult)
        {
            return optionalResult.Execute(result => result, 
                () => { throw new InvalidOperationException(); });
        }

        public static IOptionalResult<TTarget> Execute<TSource, TTarget>(
            this IOptionalResult<TSource> optionalResult, 
            Func<TSource, TTarget> func)
        {
            return optionalResult.Execute(
                t => func(t).AsOptionalResult(),
                AsOptionalResult<TTarget>);
        }

        public static IOptionalResult<TTarget> Execute<TSource, TTarget>(
            this IOptionalResult<TSource> optionalResult, 
            Func<TSource, IOptionalResult<TTarget>> func)
        {
            return optionalResult.Execute(func, AsOptionalResult<TTarget>);
        }

        private class NotExistOptionalResult<TResult> : IOptionalResult<TResult>
        {
            public TExecuteResult Execute<TExecuteResult>(
                Func<TResult, TExecuteResult> existFunc, Func<TExecuteResult> notExistFunc)
            {
                return notExistFunc();
            }
        }

        private class ExistOptionalResult<TResult> : IOptionalResult<TResult>
        {
            private readonly TResult result;

            public ExistOptionalResult(TResult result)
            {
                this.result = result;
            }

            public TExecuteResult Execute<TExecuteResult>(
                Func<TResult, TExecuteResult> existFunc, Func<TExecuteResult> notExistFunc)
            {
                return existFunc(result);
            }
        }
    }
}